/*
 * Copyright (c) 2008, Yifu Huang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of SleekIce nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.sleekice.core;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JFrame;

/**
 * The core thread that handles the updates, renders, and paints of the game.
 */
public abstract class SleekCore extends JFrame implements Runnable,
    Updatable, Renderable {

    /* the fps counter */
    protected FpsHandler fpsHandler;
    /* the game specifications */
    private GameSpecs specs;
    /* double buffer graphics
     * everything is drawn to a buffer image then the image is finally drawn
     */
    private Graphics2D dbg; /* child uses this to do graphics */
    private Image dbImage = null; /* image used in paintScreen() */
    /* the time in nano of the last game update */
    private long lastTime = 0;
    /* the thread */
    private Thread thread;
    /* When wanting to stop the thread, it is better to finish what is in the
     * loop.
     */
    private boolean running = false;

    /**
     * This method is triggered at the end of the game.
     */
    public abstract void onExit();

    /**
     * Starts the game with the given game specifications. This must be called
     * to start the core loop.
     */
    public final void gameStart(GameSpecs specs) {
        this.specs = specs;
        setSize(specs.getScreenWidth(), specs.getScreenHeight());
        setResizable(specs.getScreenResizable());
        setTitle(specs.getTitle());
        /* titlebar visibility */
        if (specs.getScreenTitleBar()) {
            setUndecorated(false);
        }
        else {
            setUndecorated(true);
        }
        /* center screen */
        if (specs.getScreenCentered()) {
            Dimension screenSize =
                Toolkit.getDefaultToolkit().getScreenSize();
            int frameX = (screenSize.width - specs.getScreenWidth()) / 2;
            int frameY = (screenSize.height - specs.getScreenHeight()) / 2;
            setLocation(frameX, frameY);
        }
        /* show the window */
        if (specs.getScreenShow()) {
            setVisible(true);
        }
        /* start the core loop */
        this.fpsHandler =
            new FpsHandler(specs.getFPSLimit(), specs.getFPSAccuracy());
        running = true;
        lastTime = System.nanoTime();
        thread = new Thread(this);
        thread.start();
    }

    /**
     * Creates the double buffer image used to do graphics rendering. This is
     * called only from the core.
     */
    private final void preRender() {
        if (dbImage == null) {
            dbImage = createImage(getWidth(), getHeight());
            if (dbImage == null) {
                System.out.println("Error creating Image Buffer");
                return;
            }
            else {
                dbg = (Graphics2D) dbImage.getGraphics();
            }
        }
    }

    /**
     * Paints the buffered image to the screen giving flicker free results.
     * This is called only from the core.
     */
    private final void paintScreen() {
        Graphics g;
        try {
            g = getGraphics();
            if (g != null && dbImage != null) {
                g.drawImage(dbImage, 0, 0, null);
                Toolkit.getDefaultToolkit().sync();
                g.dispose();
            }
        }
        catch (Exception e) {
            System.out.println("Paint Error in GamePanel");
            e.printStackTrace();
        }
    }

    /**
     * Stops the game cleanly. Allows the loop to finish and then end.
     */
    public final void stop() {
        running = false;
    }

    /**
     * Returns the running state of the game.
     * @return whether this loop is running
     */
    public final boolean isRunning() {
        return running;
    }
    /**
     * The core game loop. onExit() is called when the loop is done.
     */
    long timeNow;

    public final void run() {
        if (specs.getScreenShow()) {
            while (running) {
                fpsHandler.startFrame();
                timeNow = System.nanoTime();
                update((System.nanoTime() - lastTime) / 1000000000.0);
                lastTime = timeNow;
                preRender();
                render(dbg);
                paintScreen();
                fpsHandler.endFrame();
            }
        }
        else {
            while (running) {
                fpsHandler.startFrame();
                timeNow = System.nanoTime();
                update((System.nanoTime() - lastTime) / 1000000000.0);
                lastTime = timeNow;
                fpsHandler.endFrame();
            }
        }
        onExit();
        dispose();
    }
}